use crypto::{
    aes, blockmodes,
    buffer::{self, BufferResult, ReadBuffer, WriteBuffer},
    symmetriccipher,
};
use serde::{Deserialize, Serialize};

#[derive(Debug)]
pub enum Message {
    TextMessage(TextMessage),
    ImageMessage(ImageMessage),
    EventMessage(EventMessage),
    UnSupportMessage(UnSupportMessage),
}

/// 文本消息
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct TextMessage {
    pub to_user_name: String,
    pub from_user_name: String,
    pub create_time: i64,
    pub msg_type: String,
    pub content: String,
    pub msg_id: u64,
}

impl MessageParser for TextMessage {
    type WeChatMessage = TextMessage;

    #[inline]
    fn from_xml(xml: &str) -> Self::WeChatMessage {
        let item: TextMessage = serde_xml_rs::from_str(xml).unwrap();
        item
    }
}

/// 图片消息
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct ImageMessage {
    pub to_user_name: String,
    pub from_user_name: String,
    pub create_time: i64,
    pub msg_type: String,
    pub pic_url: String,
    pub media_id: String,
    pub msg_id: u64,
}

impl MessageParser for ImageMessage {
    type WeChatMessage = ImageMessage;

    #[inline]
    fn from_xml(xml: &str) -> Self::WeChatMessage {
        let item: ImageMessage = serde_xml_rs::from_str(xml).unwrap();
        item
    }
}

/// 事件消息
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct EventMessage {
    pub to_user_name: String,
    pub from_user_name: String,
    pub create_time: i64,
    pub msg_type: String,
    pub event: String,
}

/// 消息解析器实现
impl MessageParser for EventMessage {
    /// 微信消息类型定义
    type WeChatMessage = EventMessage;

    #[inline]
    fn from_xml(xml: &str) -> Self::WeChatMessage {
        let item: EventMessage = serde_xml_rs::from_str(xml).unwrap();
        item
    }
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct UnSupportMessage {
    pub from_user_name: String,
    pub to_user_name: String,
    pub msg_type: String,
}

impl MessageParser for UnSupportMessage {
    type WeChatMessage = UnSupportMessage;

    #[inline]
    fn from_xml(xml: &str) -> Self::WeChatMessage {
        let item: UnSupportMessage = serde_xml_rs::from_str(xml).unwrap();
        item
    }
}

/// 消息解析器
pub trait MessageParser {
    type WeChatMessage;

    fn from_xml(xml: &str) -> Self::WeChatMessage;
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct RawMessage {
    pub msg_type: String,
}

/// 解析Message信息
pub fn parse_message<S: AsRef<str>>(xml: S) -> Message {
    let xml = xml.as_ref();
    let item: RawMessage = serde_xml_rs::from_str(xml).unwrap();
    let msg_type = item.msg_type.as_str();
    let msg = match msg_type {
        "text" => Message::TextMessage(TextMessage::from_xml(xml)),
        "image" => Message::ImageMessage(ImageMessage::from_xml(xml)),
        "event" => Message::EventMessage(EventMessage::from_xml(xml)),
        _ => Message::UnSupportMessage(UnSupportMessage::from_xml(xml)),
    };
    msg
}

impl Message {
    /// 解析并返回Message对象
    pub fn parse<S: AsRef<str>>(xml: S) -> Message {
        parse_message(xml)
    }

    /// 获取消息谁发送的
    pub fn get_from_user(&self) -> String {
        match *self {
            Message::TextMessage(ref msg) => msg.from_user_name.to_owned(),
            Message::ImageMessage(ref msg) => msg.from_user_name.to_owned(),
            Message::EventMessage(ref msg) => msg.from_user_name.to_owned(),
            Message::UnSupportMessage(ref msg) => msg.from_user_name.to_owned(),
        }
    }

    /// 获取消息发送给谁
    pub fn get_to_user(&self) -> String {
        match *self {
            Message::TextMessage(ref msg) => msg.to_user_name.to_owned(),
            Message::ImageMessage(ref msg) => msg.to_user_name.to_owned(),
            Message::EventMessage(ref msg) => msg.to_user_name.to_owned(),
            Message::UnSupportMessage(ref msg) => msg.to_user_name.to_owned(),
        }
    }
}

pub trait ReplyRender {
    fn render(&self) -> String;
}

pub enum Reply {
    TextReply(TextReply),
    ImageReply(ImageReply),
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct TextReply {
    pub from_user_name: String,
    pub to_user_name: String,
    pub create_time: i64,
    pub content: String,
}

impl ReplyRender for TextReply {
    #[inline]
    fn render(&self) -> String {
        format!(
            r#"<xml><ToUserName><![CDATA[{to_user}]]></ToUserName><FromUserName><![CDATA[{from_user}]]></FromUserName><CreateTime>{time}</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[{content}]]></Content></xml>"#,
            to_user = self.to_user_name,
            from_user = self.from_user_name,
            time = self.create_time,
            content = self.content
        )
    }
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct ImageReply {
    pub from_user_name: String,
    pub to_user_name: String,
    pub create_time: i64,
    pub media_id: String,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Image {}

impl ReplyRender for ImageReply {
    #[inline]
    fn render(&self) -> String {
        format!(
            r#"<xml><ToUserName><![CDATA[{to_user}]]></ToUserName><FromUserName><![CDATA[{from_user}]]></FromUserName><CreateTime>{time}</CreateTime><MsgType><![CDATA[image]]></MsgType><Image><MediaId><![CDATA[{media_id}]]></MediaId></Image></xml>"#,
            to_user = self.to_user_name,
            from_user = self.from_user_name,
            time = self.create_time,
            media_id = self.media_id
        )
    }
}

impl Reply {
    pub fn render(&self) -> String {
        let reply = match *self {
            Reply::TextReply(ref r) => r.render(),
            Reply::ImageReply(ref r) => r.render(),
        };
        reply
    }
}

fn aes_cbc_encrypt(
    key_size: aes::KeySize,
    data: &[u8],
    key: &[u8],
    iv: &[u8],
) -> Result<Vec<u8>, symmetriccipher::SymmetricCipherError> {
    let mut encryptor = aes::cbc_encryptor(key_size, key, iv, blockmodes::PkcsPadding);

    let mut final_result = Vec::<u8>::new();
    let mut read_buffer = buffer::RefReadBuffer::new(data);
    let mut buffer = [0; 4096];
    let mut write_buffer = buffer::RefWriteBuffer::new(&mut buffer);

    loop {
        let result = (encryptor.encrypt(&mut read_buffer, &mut write_buffer, true))?;

        final_result.extend(
            write_buffer
                .take_read_buffer()
                .take_remaining()
                .iter()
                .map(|&i| i),
        );

        match result {
            BufferResult::BufferUnderflow => break,
            BufferResult::BufferOverflow => {}
        }
    }
    Ok(final_result)
}

/// Decrypts a buffer with the given key and iv using AES-256/CBC/Pkcs encryption.
/// @param1: key大小
/// @param2: 加密数据
fn aes_cbc_decrypt(
    key_size: aes::KeySize,
    encrypted_data: &[u8],
    key: &[u8],
    iv: &[u8],
) -> Result<Vec<u8>, symmetriccipher::SymmetricCipherError> {
    let mut decryptor = aes::cbc_decryptor(key_size, key, iv, blockmodes::PkcsPadding);

    let mut final_result = Vec::<u8>::new();
    let mut read_buffer = buffer::RefReadBuffer::new(encrypted_data);
    let mut buffer = [0; 4096];
    let mut write_buffer = buffer::RefWriteBuffer::new(&mut buffer);
    // println!("=== decrypt key {:?} ", key);
    loop {
        let result = (decryptor.decrypt(&mut read_buffer, &mut write_buffer, true))?;
        final_result.extend(
            write_buffer
                .take_read_buffer()
                .take_remaining()
                .iter()
                .map(|&i| i),
        );
        match result {
            BufferResult::BufferUnderflow => break,
            BufferResult::BufferOverflow => {}
        }
    }

    // println!("==== aes === result {:?}", final_result);

    Ok(final_result)
}

/// 位数不够时用0补齐
fn vec_pad(txt: Vec<u8>, length: usize) -> Vec<u8> {
    if txt.len() < length {
        let s = length - txt.len();
        let mut xs = txt;
        for _i in 0..s {
            xs.push(0u8);
        }
        return xs;
    }
    txt
}

struct AesCrypt {
    key: Vec<u8>,
    iv: Vec<u8>,
    key_size: aes::KeySize,
}

impl AesCrypt {
    /// 针对key的长度，keysize动态变化
    pub fn new(_key: Vec<u8>, iv: Vec<u8>) -> AesCrypt {
        let l = _key.len();
        let mut key = _key.clone();
        println!("l={}", l);
        let mut key_size = aes::KeySize::KeySize128;
        if l <= 16 {
            key = vec_pad(_key, 16);
        } else if l > 16 && l < 24 {
            key = _key[0..16].to_vec();
        } else if l > 24 && l < 32 {
            key_size = aes::KeySize::KeySize192;
            key = _key[0..24].to_vec();
        } else if l >= 32 {
            key_size = aes::KeySize::KeySize256;
            key = _key[0..32].to_vec();
        }
        AesCrypt {
            key: key,
            iv: iv,
            key_size: key_size,
        }
    }

    pub fn encrypt(&self, text: String) -> String {
        // aes 加密
        let encrypted_data = aes_cbc_encrypt(self.key_size, text.as_bytes(), &self.key, &self.iv)
            .ok()
            .unwrap();
        // 编码成base64
        let mut base64_encode = String::new();
        base64::encode_config_buf(&encrypted_data, base64::STANDARD, &mut base64_encode);
        base64_encode
    }

    pub fn encrypt_byte(&self, text: Vec<u8>) -> String {
        // aes 加密
        let encrypted_data = aes_cbc_encrypt(self.key_size, &text, &self.key, &self.iv)
            .ok()
            .unwrap();
        // 编码成base64
        let mut base64_encode = String::new();
        base64::encode_config_buf(&encrypted_data, base64::STANDARD, &mut base64_encode);

        base64_encode
    }

    pub fn decrypt(&self, text: String) -> String {
        let mut base64_decode = Vec::<u8>::new();

        // 如是不正确的base64则返回空
        match base64::decode_config_buf(&text, base64::STANDARD, &mut base64_decode) {
            Ok(_) => {}
            Err(_e) => {
                return "".to_owned();
            }
        };
        // println!("=== msg de text===={:?}", base64_decode);
        // aes 解码
        let decrypted_data =
            match aes_cbc_decrypt(self.key_size, &base64_decode[..], &self.key, &self.iv) {
                Ok(data) => data,
                Err(_e) => {
                    println!("base64_decode={:?}", _e);
                    return "".to_owned();
                }
            };

        // 转换成string
        let the_string = String::from_utf8(decrypted_data).expect("not UTF-8");
        the_string
    }
}
pub struct EncryptMsg {
    token: String,
    aes_key: String,
}

impl EncryptMsg {
    pub fn new(token: String, aes_key: String) -> Self {
        Self { token, aes_key }
    }
    // 加密后的xml数据
    pub fn encrypt_msg(&self, reply_msg: String, nonce: String, timestamp: String) -> String {
        "".to_string()
    }
    // 从xml解密后的数据
    pub fn decrypt_msg(
        &self,
        sign: String,
        post_data: String,
        nonce: String,
        timestamp: String,
    ) -> String {
        "".to_string()
    }
}

#[cfg(test)]
mod test {
    use crate::mp::{ImageReply, Reply};

    use super::TextReply;
    #[test]
    fn test_text_xml() {
        let r = TextReply {
            from_user_name: "from".to_string(),
            to_user_name: "to".to_string(),
            create_time: 10,
            content: "test".to_string(),
        };

        let rr = Reply::TextReply(r).render();
        assert_eq!(rr, "<xml><ToUserName><![CDATA[to]]></ToUserName><FromUserName><![CDATA[from]]></FromUserName><CreateTime>10</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[test]]></Content></xml>".to_string());
    }
    #[test]
    fn test_image_xml() {
        let r = ImageReply {
            from_user_name: "from".to_string(),
            to_user_name: "to".to_string(),
            create_time: 10,
            media_id: "media_id".to_string(),
        };

        let rr = Reply::ImageReply(r).render();
        assert_eq!(rr, "<xml><ToUserName><![CDATA[to]]></ToUserName><FromUserName><![CDATA[from]]></FromUserName><CreateTime>10</CreateTime><MsgType><![CDATA[image]]></MsgType><Image><MediaId><![CDATA[media_id]]></MediaId></Image></xml>".to_string());
    }
}
